<?php

/*

    MMetaverse - Open Metaverse Access Library

    Copyright (c) 2009 Mary Jane Soft - Marlon J. Manrique

    http://www.maryjanesoft.com
    http://www.marlonj.com

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General
    Public License along with this library; if not, write to the
    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
    Boston, MA  02111-1307  USA

    $Id$

*/

/**
 * @package mjs.mmetaverse.client
 */

require_once dirname(__FILE__) . '/../packet/MPackets.class.php';
require_once dirname(__FILE__) . '/../types/MQuaternion.class.php';
require_once dirname(__FILE__) . '/../types/MUUID.class.php';
require_once dirname(__FILE__) . '/../types/MVector3.class.php';

/**
 * Metaverse Network Client 
 */
class MNetworkClient {

    /**
     * Simulator ip
     *
     * @var string
     */
    private $server;

    /**
     * Simulator port
     *
     * @var integer
     */
    private $port;

    /**
     * Circuit Code
     *
     * @var integer
     */
    private $circuitCode;

    /**
     * Agent ID
     *
     * @var string 
     */
    private $agentID;

    /**
     * Session ID
     *
     * @var string 
     */
    private $sessionID;

    /**
     * Socket connection
     *
     * @param resource 
     */
    private $socket;

    /**
     * Sequence number
     *
     * @var integer 
     */
    private $sequenceNumber;

    /**
     * Create a new netowrk client
     *
     * @param string $server server ip or name
     * @param integer $port connection port
     * @param string $agentID agent uuid
     * @param string $sessionID session uuid
     */
    public function MNetworkClient($server, $port, $circuitCode,
        $agentID, $sessionID) {
        $this->server = $server;
        $this->port = $port;
        $this->circuitCode = $circuitCode;
        $this->agentID = $agentID;
        $this->sessionID = $sessionID;

        // Init the sequence number 
        $this->sequenceNumber = 1;

        // Open the socket connection
        $this->openSocket();
    }

    /**
     * Open the socket connection
     */
    private function openSocket() {
        // Create a socket 
        $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);

        // Connect the socket
        socket_connect($this->socket, $this->server, $this->port);
    }

    /**
     * Send the packet
     *
     * @param MPacket packet
     */
    public function sendPacket(MPacket $packet) {
        // Set sequence number
        $packet->setSequenceNumber($this->sequenceNumber);

        // Get the plain data
        $data = $packet->getRowData();

        // Send datagram 
        socket_sendto($this->socket,
            $data, strlen($data), 0,
            $this->server, $this->port);

        // Update sequence number 
        $this->sequenceNumber++;
    }

    /**
     * Close the network client 
     */
    private function close() {
        socket_close($this->socket);
    }

    /**
     * Complete login process
     */
    public function login() {
        // Send Use Circuit Code 
        $packet = MPackets::createUseCircuitCodePacket(
            $this->circuitCode, $this->agentID, $this->sessionID);
        $this->sendPacket($packet);

        // Adding some delay between messages 
        sleep(2);

        // Send Complete Agent Movement 
        $packet = MPackets::createCompleteAgentMovementPacket(
            $this->circuitCode, $this->agentID, $this->sessionID);
        $this->sendPacket($packet);

        // Send Agent Update, is not require by OpenSim ??
        // $this->sendAgentUpdate();
    }

    /**
     * Logout from the server
     */
    public function logout() {
        // Send Use Circuit Code
        $packet = MPackets::createLogoutRequest(
            $this->agentID, $this->sessionID);
        $this->sendPacket($packet);

        // Close the socket
        $this->close();
    }

    /**
     * Send AgentUpdate packet
     */
    private function sendAgentUpdate()
    {
        // Set request values 
        $bodyRotation = new MQuaternion();
        $headRotation = new MQuaternion();
        $state = 0;
        $cameraCenter = new MVector3();
        $cameraAtAxis = new MVector3();
        $cameraLeftAxis = new MVector3();
        $far = 0;
        $controlFlags = 0;
        $flags = 0;

        // Create package 
        $packet = MPackets::createAgentUpdate(
            $this->agentID, $this->sessionID,
            $bodyRotation, $headRotation,
            $state, $cameraCenter, $cameraAtAxis, $cameraLeftAxis,
            $far, $controlFlags, $flags);

        // Send packet 
        $this->sendPacket($packet);
    }

    /**
     * Receive a packet from the server
     */
    public function receivePacket() {
        // Receive Data
        socket_recvfrom($this->socket, $data, 12, 0, $from, $port);

        // Create packet 
        $packet = MPacket::createPacket($data);

        // If packet needs ack, send it now 
        if($packet->isReliable())
            $this->sendAck($packet->getNumber());

        // Check start ping check
        if($packet->getFrequency() == MPacket::FREQUENCY_HIGH &&
            $packet->getMessageNumber() == 0x01) {
            // Respond ping check 
            $this->sendCompletePingCheck($packet->getU8());
        }
    }

    /**
     * Send packet acknowledge
     *
     * @param integer $packetNumber
     */
    public function sendAck($packetNumber)
    {
        // Create and send packetack 
        $packet = MPackets::createPacketAck($packetNumber);
        $this->sendPacket($packet);
    }

    /**
     * Send a complete ping check
     *
     * @param integer $pingID Ping identifier
     */
    public function sendCompletePingCheck($pingID)
    {
        // Create and send packetack
        $packet = MPackets::createCompletePingCheck($pingID);
        $this->sendPacket($packet);
    }
}

?>